<script setup lang="ts">
import Prism from 'prismjs';
import { onMounted } from 'vue';
onMounted(() => {
  Prism.highlightAll();
});
</script>

<template>
  <div class="websocket-page">
    <h2>封装webSocket单例模式，支持心跳检测，断线重连：</h2>
    <div class="code">
      <div class="created">
        <div class="pre">
          <pre class="line-numbers">
        <code class='language-js'>
          interface heartBaetMsgType {
  msg: 'ping'
  time: number
}

class WebsocketClient {
  private static instance: WebsocketClient
  private socket!: WebSocket
  private heartBeat: { interval: number; reconnect: number } = {
    interval: 5 * 1000, // 心跳检测时间
    reconnect: 10 * 1000, // 断线重连时间
  }
  private webSocketState: boolean = false
  private heartBeatMsg: heartBaetMsgType = {
    // 心跳消息
    msg: 'ping',
    time: Date.now(),
  }
  private heartBeatTimeout: number = 10 * 1000 // 心跳超时时间

  constructor(public url: string) {
    this.connect()
  }

  public static getInstance(url: string): WebsocketClient {
    if (!WebsocketClient.instance) {
      WebsocketClient.instance = new WebsocketClient(url)
    }
    return WebsocketClient.instance
  }

  connect() {
    this.socket = new WebSocket(this.url)
    this.init()
  }

  init() {
    this.socket.onopen = () => {
      console.log('ws打开链接')
      this.webSocketState = true
      // 检查依赖数据 开始心跳检测
      this.heartBeat && this.heartBeat.interval ? this.startHeartBeat(this.heartBeat.interval) : ''
    }

    this.socket.onmessage = (event: {{ 'MessageEvent<any>' }}) => {
      console.log('接收消息: ', event.data)
      switch (event.data) {
        case 'ping':
          console.log('心跳消息')
          this.startHeartBeat(this.heartBeat.interval)
          break
        case 'msg':
          console.log('其他消息')
          break
      }
    }

    this.socket.onclose = () => {
      console.log('ws断开连接')
      this.webSocketState = false
      this.reconnect()
    }

    this.socket.onerror = (error: Event) => {
      console.log('ws-err: ', error)
      this.webSocketState = false
      this.reconnect()
    }
  }

  {{ 'sendMessage<T>' }}(msg: T) {
    if (this.socket.readyState === WebSocket.OPEN) {
      return this.socket.send(JSON.stringify(msg))
    }
    console.error('webSocket未连接')
    this.reconnect()
  }

  // 心跳检测
  startHeartBeat(t: number) {
    setTimeout(() => {
      {{ 'this.sendMessage<heartBaetMsgType>' }}(this.heartBeatMsg) // 发送心跳消息
      this.waitingServer() // 延迟等待服务端响应
    }, t)
  }

  // 延迟等待服务端响应
  waitingServer() {
    setTimeout(() => {
      if (this.webSocketState) {
        this.startHeartBeat(this.heartBeat.interval) // 没有断开继续心跳
        return
      }
      this.socket.close()
      this.reconnect() // 断开重连
    }, this.heartBeatTimeout)
  }

  // 重连
  reconnect() {
    setTimeout(() => {
      this.connect()
    }, this.heartBeat.reconnect)
  }
}

export default WebsocketClient.getInstance
        </code>
      </pre>
        </div>
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.websocket-page {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  height: 100vh;
  overflow: auto;
}

.pre {
  width: 60vw;
}

pre[class*="language-"] {
  background-color: #000;
  padding: 1em;
  border-radius: 5px;
}

/* 设置高亮行的背景颜色 */
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
  background: none;
  color: #e91e63;
}
</style>
